var RiseVision = RiseVision || {};

RiseVision.Common = RiseVision.Common || {};
RiseVision.Common.Settings = {};
RiseVision.Common.Visualization = {};
RiseVision.Common.Financial = {};
RiseVision.Common.Financial.Helper = {};
RiseVision.Common.Sports = {};
RiseVision.Common.HorizontalScroll = {};
RiseVision.Common.Item = {};
RiseVision.Common.Scroller = {};
RiseVision.Common.Utility = {};

/*
 * Validation functions for custom Gadget settings.
 */
RiseVision.Common.Settings = function() {}
RiseVision.Common.Settings.prototype.validateRequired = function ($element, errors, fieldName) {
    //Don't validate element if it's hidden.
    if (!$element.is(":visible")) {
	return false;
    }
    else {
	if (!$.trim($element.val())) {	
	    errors.innerHTML += fieldName + " is a required field.<br />";
	    return true;
	}
	else {
	    return false;
	}
    }
}
RiseVision.Common.Settings.prototype.validateNumeric = function ($element, errors, fieldName) {
    //Don't validate element if it's hidden.
    if (!$element.is(":visible")) {
	return false;
    }
    else {
	if (isNaN($element.val())) {
	    errors.innerHTML += "The " + fieldName + " field must contain only numbers.<br />";
	    return true;
	}
	else {
	    return false;
	}
    }
}

/*
 * Use the Google Visualization API to 
 */
RiseVision.Common.Visualization = function() {
    this.isVisualizationLoaded = false;
}
RiseVision.Common.Visualization.prototype.getData = function(opts) {
    this.query = null;
    this.url = opts.url;
    this.refreshInterval = opts.refreshInterval;
    this.callback = opts.callback;    
    
    if (opts.queryString) {
	this.queryString = opts.queryString;
    }
    
    //For some reason, trying to load the Visualization API more than once does not execute the callback function.
    if (!this.isVisualizationLoaded) {
	this.loadVisualizationAPI();
    }
    else {
        this.sendQuery();
    }
}
RiseVision.Common.Visualization.prototype.loadVisualizationAPI = function() {
    var self = this;
    
    google.load("visualization", "1", {"callback" : function() {
	self.isVisualizationLoaded = true;
	self.sendQuery();
    }});
}
RiseVision.Common.Visualization.prototype.sendQuery = function() {
    var self = this;
    
    if (this.query != null) {
        this.query.abort();
    }

    this.query = new google.visualization.Query(this.url);
    this.query.setRefreshInterval(this.refreshInterval);
    
    if (this.queryString) {
	this.query.setQuery(this.queryString);
    }
    
    this.query.send(function(response) {
	if (response == null) {
	    self.callback(response);
	}
	else {
	    if (response.isError()) {
		console.log(response.getDetailedMessage());
		console.log(response.getReasons());
		self.callback(null);
	    }
	    else {
		self.callback(response.getDataTable());
	    }
	}
    });
}

RiseVision.Common.Financial.Helper = function(instruments) {
    this.instruments = instruments;
}
RiseVision.Common.Financial.Helper.prototype.setInstruments = function(instruments) {
    this.instruments = instruments;
}
RiseVision.Common.Financial.Helper.prototype.getInstruments = function(isLoading, collectionTimes) {
    var self = this;
    
    if (isLoading) {
	return this.instruments.join("|");
    }
    else {
	var dayOfWeek = new Date().getDay(),
	    len = collectionTimes.length,
	    instruments = [];
	    
	$.each(this.instruments, function(i, instrument) {
	    for (var j = 0; j < len; j++) {
		if (instrument == collectionTimes[j].instrument) {
		    var today = new Date(),
			hours = today.getHours(),
			minutes = today.getMinutes(),
			startTime = collectionTimes[j].startTime,
			endTime = collectionTimes[j].endTime,
			daysOfWeek = collectionTimes[j].daysOfWeek;
		    
		    //Check if the instrument should be requested again based on its collection data.
		    $.each(daysOfWeek, function(j, day) {
			//Check collection day.
			if (day == dayOfWeek) {		    
			    //Check collection time.
			    if (new Date("Jan 1, 1970 " + hours + ":" + minutes).between(startTime, endTime)) {
				instruments.push(self.instruments[i]);
			    }
			    
			    return false;
			}
		    });
		}
	    }	    
	});
	
	return instruments.join("|");
    }
}

RiseVision.Common.Financial.RealTime = function(displayID, instruments) {
    var self = this;
    
    if (displayID) {
	this.displayID = displayID;
    }
    else {
	this.displayID = "preview";
    }
        
    //Trim any whitespace from instruments.
    instruments = instruments.split(",");
	
    $.each(instruments, function(index, value) {
	instruments[index] = $.trim(instruments[index]);
    });
    
    this.instruments = instruments;  
    this.isLoading = true;
    this.conditions = {};
    this.updateInterval = 60000;
    this.url = "http://contentfinancial2.appspot.com/data?";
    this.logosURL = "https://s3.amazonaws.com/risecontentlogos/financial/";
    this.viz = new RiseVision.Common.Visualization();
    this.collectionTimes = [];
    this.helper = new RiseVision.Common.Financial.Helper(this.instruments);
}
RiseVision.Common.Financial.RealTime.prototype.setInstruments = function(instruments) {
    //Trim any whitespace from instruments.
    instruments = instruments.split(",");
	
    $.each(instruments, function(index, value) {
	instruments[index] = $.trim(instruments[index]);
    });
    
    this.isLoading = true;
    this.collectionTimes = [];
    this.instruments = instruments;    
    this.helper.setInstruments(this.instruments);
}
/* fields is an array of fields to request from data server. Note: instrument column is always requested. */
/* Financial Data */
RiseVision.Common.Financial.RealTime.prototype.getData = function(fields, loadLogos, callback) {
    var self = this,
	duplicateFound = false,
	fieldCount = 0,
	queryString = "select instrument",
	codes = "";
	
    this.dataFields = {};
    this.dataFields["instrument"] = 0;
    //TODO: Get rid of startTimeIndex and append instruments as last column?
    this.startTimeIndex = 1;	//Used to determine where collection data columns are.
    
    //Build the query string.
    $.each(fields, function(index, field) {
	duplicateFound = false;
	
	//Do nothing as instrument is already being requested.
	if (field == "instrument") {
	}
	else {
	    //Visualization API doesn't allow requesting the same field more than once.
	    $.each(self.dataFields, function(i, dataField) {
		if (i == field) {
		    duplicateFound = true;
		    return false;
		}
	    });
	    
	    if (!duplicateFound) {
		queryString += ", " +  field;
		//Create a mapping between field names and column indices.
		self.dataFields[field] = fieldCount + 1;
		fieldCount++;
		self.startTimeIndex++;
	    }
	}
    });
    
    this.logoCount = 0;
    queryString += ", startTime, endTime, daysOfWeek, timeZoneOffset"
    codes = this.helper.getInstruments(this.isLoading, this.collectionTimes);
    
    //Perform a search for the instruments.
    if (codes) {
	var self = this,
	    options = {
		url: this.url + "id=" + this.displayID + "&codes=" + codes,
		refreshInterval: 0,
		queryString: queryString,
		callback: function(data) {
		    if (data != null) {
			clearTimeout(self.getDataTimer);
			self.data = data;
			self.callback = callback;
			
			if (self.isLoading) {
			    self.isLoading = false;
			
			    if (self.collectionTimes.length == 0) {
				self.saveCollectionTimes();
			    }
			    
			    if (loadLogos) {
				self.loadLogos();
			    }
			    else {
				if (self.callback) {
				    self.callback(self.data);
				} 
			    }
			}
			else {
			    if (self.callback) {
				self.callback(self.data);
			    } 
			}
		    }
		}
	    };
	
	//Start a timer in case there is a problem loading the data (i.e. Internet has been disconnected).
	this.getDataTimer = setTimeout(function() {
	    self.getData(fields, loadLogos, callback);
	}, this.updateInterval);
	
	this.viz.getData(options);
    }
    else {
	callback(null);
    }
}
RiseVision.Common.Financial.RealTime.prototype.saveCollectionTimes = function() {	
    var numRows, timeZoneOffset, startTime, endTime, isDaylightSavingTime;
	    
    if (this.data != null) {
	numRows = this.data.getNumberOfRows();
	isDaylightSavingTime = Date.today().isDaylightSavingTime();
	
	//Only need to save collection time once for the entire chain.
	//Use the collection data from the first stock since the rest should all be the same.
	//Data is for a chain if there is only one instrument being requested, but multiple rows of data are returned.
	if ((this.instruments.length == 1) && (this.data.getNumberOfRows() > 1)) {
	    if ((this.data.getValue(0, 0) != "INVALID_SYMBOL")) {
		timeZoneOffset = this.data.getValue(0, this.startTimeIndex + 3);
		startTime = this.data.getValue(0, this.startTimeIndex);    	
		endTime = this.data.getValue(0, this.startTimeIndex + 1);
		
		if (startTime && endTime && timeZoneOffset != "N/P") {
		    if (isDaylightSavingTime) {
			startTime.addHours(1);
			endTime.addHours(1);
		    }
			
		    this.collectionTimes.push({
			"instrument": this.instruments[0],	    
			"startTime": startTime.setTimezoneOffset(timeZoneOffset),
			"endTime": endTime.setTimezoneOffset(timeZoneOffset),
			"daysOfWeek": this.data.getFormattedValue(0, this.startTimeIndex + 2).split(",")
		    });
		}
	    }
	}
	//Save collection data for each stock.
	else {
	    for (var row = 0; row < numRows; row++) {
		if (this.data.getValue(row, 0) != "INVALID_SYMBOL") {
		    timeZoneOffset = this.data.getValue(row, this.startTimeIndex + 3);
		    startTime = this.data.getValue(row, this.startTimeIndex);
		    endTime = this.data.getValue(row, this.startTimeIndex + 1);
		    
		    if (startTime && endTime && timeZoneOffset != "N/P") {
			if (isDaylightSavingTime) {
			    startTime.addHours(1);
			    endTime.addHours(1);
			}
			
			this.collectionTimes.push({
			    "instrument": this.instruments[row],
			    "startTime": startTime.setTimezoneOffset(timeZoneOffset),
			    "endTime": endTime.setTimezoneOffset(timeZoneOffset),
			    "daysOfWeek": this.data.getFormattedValue(row, this.startTimeIndex + 2).split(",")
			});
		    }
		}
	    }
	}
    }
}
RiseVision.Common.Financial.RealTime.prototype.loadLogos = function() {
    var self = this,	
	numRows = this.data.getNumberOfRows();
    
    //Load all company logos.
    for (var row = 0; row < numRows; row++) {	
	var img = new Image();
	
	img.onload = function () {
	    self.onLogoLoaded();
	}
	
	img.onerror = function() {
	    self.onLogoLoaded();
	}
	
	img.src = this.logosURL + this.data.getValue(row, 0) + ".svg";
    }    
}
RiseVision.Common.Financial.RealTime.prototype.onLogoLoaded = function() {
    this.logoCount++;
    
    //One logo per row.
    if (this.logoCount >= this.data.getNumberOfRows()) {
	if (this.callback) {
	    this.callback(this.data);
	} 
    }
}
/* Conditions */
RiseVision.Common.Financial.RealTime.prototype.checkSigns = function(field) {
    var row = 0,
	signs = [],
	current, sign;
    
    for (row = 0, numRows = this.data.getNumberOfRows(); row < numRows; row++) {
	current = this.data.getValue(row, this.dataFields[field]);

	if (isNaN(current)) {
	    current = current.replace(/[^0-9\.-]+/g,"");
	    current = parseFloat(current);
	}

	if (!isNaN(current)) {
	    if (current >= 0) {
		sign = 1;
	    }
	    else {
		sign = -1;
	    }
	    
	    signs.push(sign);
	}
    }
    
    return signs;
}
/* Return 1 if current value is greater than the previous value.
   Return 0 if both values are equal.
   Return -1 if current value is less than the previous value. */
RiseVision.Common.Financial.RealTime.prototype.compare = function(field) {
    var self = this,
	current = 0,
	previous = 0,
	result = [],
	matchFound = false;
    
    if (this.conditions[field]) {
	for (row = 0, numRows = this.data.getNumberOfRows(); row < numRows; row++) {
	    current = this.data.getValue(row, this.dataFields[field]);
	    matchFound = false;
		
	    $.each(this.conditions[field], function(index, value) {
		//Instrument is used to ensure that the rows that are being compared are for the same stock.
		//In chains, rows may be added or deleted.
		if (value.instrument == self.data.getValue(row, 0)) {
		    previous = value.value;
		    
		    if (isNaN(current)) {
			current = current.replace(/[^0-9\.-]+/g,"");
			current = parseFloat(current);
		    }
		    
		    if (isNaN(previous)) {
			previous = previous.replace(/[^0-9\.-]+/g,"");
			previous = parseFloat(previous);
		    }	
	    
		    //The data type of a column can still be a number even if there is string data in it.
		    //To be sure, let's check that the values we are comparing are numbers.
		    if (!isNaN(current) && !isNaN(previous)) {
			if (current != previous) {
			    if (current > previous) {
				result.push(1);
			    }
			    else {
				result.push(-1);
			    }
			}
			//They are equal.
			else {
			    result.push(0);
			}
		    }
	    
		    matchFound = true;
		    
		    return false;
		}
	    });
	    
	    //No match found for this instrument (ie it's new).
	    if (!matchFound) {
		result.push(0);
	    }
	}
    }
    
    this.saveBeforeValues([field]);
    
    return result;
}
RiseVision.Common.Financial.RealTime.prototype.saveBeforeValues = function(fields) {
    var self = this;
    
    $.each(fields, function(index, value) {
	self.conditions[value] = [];
	self.saveBeforeValue(value, self.dataFields[value]);
    });
}
/* Store the current values so they can be compared to new values on a refresh. */
RiseVision.Common.Financial.RealTime.prototype.saveBeforeValue = function(field, colIndex) {
    for (var row = 0, numRows = this.data.getNumberOfRows(); row < numRows; row++) {
	this.conditions[field].push({
	    "instrument": this.data.getValue(row, 0),
	    "value": this.data.getValue(row, colIndex)
	});
    }				
}

RiseVision.Common.Financial.Historical = function(displayID, instrument, duration) {
    var self = this;
    
    if (displayID) {
	this.displayID = displayID;
    }
    else {
	this.displayID = "preview";
    }
    
    this.instrument = instrument;        
    this.duration = duration;    
    this.isLoading = true;
    this.updateInterval = 60000;
    this.url = "http://contentfinancial2.appspot.com/data/historical?";
    this.historicalViz = new RiseVision.Common.Visualization();
    this.collectionTimes = [];
    this.helper = new RiseVision.Common.Financial.Helper([this.instrument]);
}
RiseVision.Common.Financial.Historical.prototype.setInstrument = function(instrument) {
    this.isLoading = true;
    this.collectionTimes = [];
    this.instrument = instrument;    
    this.helper.setInstruments([this.instrument]);
}
RiseVision.Common.Financial.Historical.prototype.setDuration = function(duration) {
    this.duration = duration;       
}
/* Historical Financial data - Only one stock can be requested at a time. */
RiseVision.Common.Financial.Historical.prototype.getHistoricalData = function(fields, callback, options) {
    var self = this,
	queryString = "select " + fields.join() + " ORDER BY tradeTime",
	codes = "",
	numDataRows = 0;
	
    //Customize the query string.
    if (options) {
	if (options.sortOrder) {
	    if (options.sortOrder == "desc") {
		queryString += " desc";
	    }
	}
	
	if (options.limit) {
	    queryString += " LIMIT " + options.limit;
	}
    }
    
    codes = this.helper.getInstruments(this.isLoading, this.collectionTimes);
    
    //Perform a search for the instrument.
    if (codes) {    
	options = {
	    url: this.url + "id=" + this.displayID + "&code=" + this.instrument + "&kind=" + this.duration,
	    refreshInterval: 0,
	    queryString: queryString,
	    callback: function(data) {
		if (data != null) {
		    self.historicalData = data;
		    numDataRows = data.getNumberOfRows();
	
		    if ((numDataRows == 0) || ((numDataRows == 1) && (data.getFormattedValue(0, 0) == "0"))) {
			self.isLoading = true;
		    }
		    else {
			self.isLoading = false;
		    }
		    
		    clearTimeout(self.getHistoricalDataTimer);
		    
		    self.getHistoricalTimes(function(result) {
			callback(result);
		    });
		}
	    }
	};
	
	//Start a timer in case there is a problem loading the data (i.e. Internet has been disconnected).
	this.getHistoricalDataTimer = setTimeout(function() {
	    self.getHistoricalData(fields, callback, options);
	}, this.updateInterval);
	
	this.historicalViz.getData(options);
    }
    else {
	callback(null);
    }
}
RiseVision.Common.Financial.Historical.prototype.getHistoricalTimes = function(callback) {
    var self = this,
	options = {
	    url: "http://contentfinancial2.appspot.com/info?codes=" + this.instrument,
	    refreshInterval: 0,
	    queryString: "select startTime, endTime, daysOfWeek, timeZoneOffset, updateInterval",
	    callback: function(result) {
		if (result != null) {
		    clearTimeout(self.getHistoricalTimesTimer);
		    self.historicalTimes = result;
		    
		    //Issue 799
		    if (self.collectionTimes.length == 0) {
			self.saveHistoricalTimes();
			
			if (self.historicalData != null) {
			    callback({
				"data": self.historicalData,
				"collectionData": self.historicalTimes
			    });
			}
			else {
			    callback({
				"collectionData": self.historicalTimes
			    });
			}
		    }
		    else {
			if (self.historicalData != null) {
			    callback({
				"data": self.historicalData,
				"collectionData": self.historicalTimes
			    });
			}
			else {
			    callback({
				"collectionData": self.historicalTimes
			    });
			} 
		    }
		}		
	    }
	};
	
    //Start a timer in case there is a problem loading the data (i.e. Internet has been disconnected).
    this.getHistoricalTimesTimer = setTimeout(function() {
	self.getHistoricalTimes(callback);
    }, this.updateInterval);
    
   this.historicalViz.getData(options);
}
RiseVision.Common.Financial.Historical.prototype.saveHistoricalTimes = function() {
    var numRows, isDaylightSavingTime, startTime, endTime, timeZoneOffset;
	
    if (this.historicalTimes != null) {
	numRows = this.historicalTimes.getNumberOfRows();
	isDaylightSavingTime = Date.today().isDaylightSavingTime();
	
	for (var row = 0; row < numRows; row++) {	
	    startTime = this.historicalTimes.getValue(row, 0);
	    endTime = this.historicalTimes.getValue(row, 1);
	    timeZoneOffset = this.historicalTimes.getValue(row, 3);
	    
	    if (isDaylightSavingTime) {
		startTime.addHours(1);
		endTime.addHours(1);
	    }
	    
	    this.collectionTimes.push({
		"instrument": this.instrument,
		"startTime": startTime.setTimezoneOffset(timeZoneOffset),
		"endTime": endTime.setTimezoneOffset(timeZoneOffset),
		"daysOfWeek": this.historicalTimes.getFormattedValue(row, 2).split(",")
	    });		
	}
    }
}

RiseVision.Common.Sports = function(displayID) {
    var self = this;
    
    if (displayID) {
	this.displayID = displayID;
    }
    else {	//Use dummy ID.
	this.displayID = "xxx";
    }
    
    this.sport = prefs.getString("sport");
    this.collegeGames = prefs.getString("collegeGames");
    this.allSportsIndex = 0;
    this.updateInterval = 60000;
    this.allSports = ["NFL", "MLB", "NBA", "NHL", "CFOOT", "CBASK"];
}
RiseVision.Common.Sports.prototype.getData = function() {
    var self = this;
    
    this.wasReset = false;
    
    if (this.sport == "ALL") {
	this.processData("http://contentsports.appspot.com/sports/" + this.allSports[this.allSportsIndex] + ".aspx?View=Scoreboard&LogoStyle=Default" + this.getGroup() + "&DisplayId=" + this.displayId, function() {
	    self.onSportLoaded();
	});
    }
    else {
	var group = "";
	
	if ((this.sport == "CFOOT") || (this.sport == "CBASK")) {
	    group = "&Group=" + this.collegeGames;
	}
	
	this.processData("http://contentsports.appspot.com/sports/" + this.sport + ".aspx?View=Scoreboard&LogoStyle=Default" + group + "&DisplayId=" + this.displayID, function() {
	    self.getLogoURLs();   
	});
    }
}
RiseVision.Common.Sports.prototype.processData = function(url, callback) {
    var	self = this,
	params = {};
	
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
    params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = 60;
    gadgets.io.makeRequest(url, function(obj) {
	if (obj.data) {
	    var data = obj.data,
		game = data.getElementsByTagName("Game"),
		sportCode = "", sportName, i;
		
	    if (!self.wasReset) {
		self.sports = [];
		self.games = [];
		self.logoCount = 0;
		self.totalLogos = 0;
		self.wasReset = true;
	    }
	    
	    if (data) {
		if (data.getElementsByTagName("Sport").length > 0) {
		    sportCode = data.getElementsByTagName("Sport")[0].getAttribute("Code");
		}
		
		if (game.length > 0) {
		    sportName = self.getNodeValue(data.getElementsByTagName("Title"));
    
		    self.totalLogos++;
		    self.sports.push({
			sportCode: sportCode,
			name: sportName
		    });
		}
		
		//Get game stats.
		for (i = 0; i < game.length; i++) {
		    var status = "",
			extendedStatus = "",
			team = game[i].getElementsByTagName("Team"),
			overUnder = "",
			host, visitor, home, j, date;
			
			if (game[i].getElementsByTagName("Status").length > 0) {
			    status = game[i].getElementsByTagName("Status")[0].getAttribute("Code");
			}
			
			extendedStatus = self.getNodeValue(game[i].getElementsByTagName("Extended"));
	
			if (game[i].getElementsByTagName("Over_Under").length > 0) {
			    if (game[i].getElementsByTagName("Over_Under")[0].getElementsByTagName("Casino").length > 0) {
				overUnder = "O/U " + game[i].getElementsByTagName("Over_Under")[0].getElementsByTagName("Casino")[0].getAttribute("value");
			    }
			}
			
			date = self.getNodeValue(game[i].getElementsByTagName("Date"));
			
			if (date) {
			    date = Date.parse(date);
			}
			
			for (j = 0; j < team.length; j++) {
			    host = team[j].getAttribute("Host");
			    
			    if (host == "Visiting") {
				visitor = self.getTeamInfo(team[j], sportCode);
			    }
			    else if (host == "Home") {
				home = self.getTeamInfo(team[j], sportCode);
			    }		    
			}
		    
		    self.games.push({
			sportCode: sportCode,
			date: date,
			status: status,
			extendedStatus: extendedStatus,
			overUnder: overUnder,
			visitor: visitor,
			home: home
		    });
		}
		
		callback();
	    }
	    else {
		console.log("Invalid data source URL");
	    }
	}
	else {
	    setTimeout(function() {
		$(self).trigger("updateTimerExpired");
	    }, self.updateInterval);
	}
    }, params);
}
RiseVision.Common.Sports.prototype.onSportLoaded = function() {
    var self = this, group;
    
    this.allSportsIndex++;
    
    group = this.getGroup();
    
    if (this.allSportsIndex == this.allSports.length) {	//All games for all sports have been loaded.
	this.allSportsIndex = 0;
	this.getLogoURLs();
    }
    else {	//Load games for next sport.
	this.processData("http://contentsports.appspot.com/sports/" + this.allSports[this.allSportsIndex] + ".aspx?View=Scoreboard&LogoStyle=Default" + group + "&DisplayId=" + this.displayID, function() {
	    self.onSportLoaded();
	});
    }
}
RiseVision.Common.Sports.prototype.getGroup = function() {
    var group = "";
    
    if ((this.allSports[this.allSportsIndex] == "CFOOT") || (this.allSports[this.allSportsIndex] == "CBASK")) {
	group = "&Group=" + this.collegeGames;
    }

    return group;
}
RiseVision.Common.Sports.prototype.getTeamInfo = function(team, sportCode) {
    var spread = "",
	img = new Image();
    
    if (team.getElementsByTagName("Casino").length > 0) {
	spread = team.getElementsByTagName("Casino")[0].getAttribute("value");
    }
    
    this.totalLogos++;
    
    return {
	name: this.getNodeValue(team.getElementsByTagName("Name")),
	rank: this.getNodeValue(team.getElementsByTagName("Rank")),
	overall: this.getNodeValue(team.getElementsByTagName("Overall")),
	score: this.getNodeValue(team.getElementsByTagName("Total")),
	spread: spread
    };
}
RiseVision.Common.Sports.prototype.getNodeValue = function(node) {
    if (node.length > 0) {
	if (node[0].childNodes.length > 0) {
	    return node[0].childNodes[0].nodeValue;
	}
	else {
	    return "";
	}
    }
    
    return "";
}
RiseVision.Common.Sports.prototype.getLogoURLs = function() {
    var filter = "", self = this;
    
    if (this.sports.length > 0) {
	$.each(this.sports, function(index, value) {
	    if (filter == "") {
		filter = " where upper(A) = '" + value.sportCode + "'";
	    }
	    else {
		filter += " or upper(A) = '" + value.sportCode + "'";
	    }
	});
	
	var settings = {
	    url: "https://docs.google.com/a/risevision.com/spreadsheet/tq?key=0AnsiFJce3EYCdHA5NWp2VVVxV1pZUFFHMmpiUGx1TXc&gid=1&headers=1",
	    refreshInterval: 0,
	    queryString: "select A, B, C" + filter,
	    callback: function(data) {
		var len = data.getNumberOfRows();
		    
		for (var row = 0; row < len; row++) {
		    if ($.trim(data.getValue(row, 1).toLowerCase()) == "sport") {
			//Match logo to proper sport.
			$.each(self.sports, function(index, value) {
			    if ($.trim(value.sportCode.toLowerCase()) == $.trim(data.getValue(row, 0).toLowerCase())) {
				value.logo = data.getValue(row, 2);
				return false;
			    }
			});
		    }
		    else {
			//Match logo to proper team.
			$.each(self.games, function(index, value) {
			    if ($.trim(value.sportCode.toLowerCase()) == $.trim(data.getValue(row, 0).toLowerCase())) {
				if ($.trim(value.visitor.name.toLowerCase()) == $.trim(data.getValue(row, 1).toLowerCase())) {
				    value.visitor.logo = data.getValue(row, 2);
				    //return false;	Issue 591 - Same team may appear in games more than once.
				}
				
				if ($.trim(value.home.name.toLowerCase()) == $.trim(data.getValue(row, 1).toLowerCase())) {
				    value.home.logo = data.getValue(row, 2);
				    //return false;	Issue 591 - Same team may appear in games more than once.
				}
			    }
			});
		    }
		}
		
		self.loadLogos();
	    }
	};
	
	getFilteredData(settings);
    }
    else {
	$(this).trigger("noData");
    }
}
RiseVision.Common.Sports.prototype.loadLogos = function() {
    var self = this;
    
    //Load all sports logos.
    $.each(this.sports, function(index, value) {	
	self.loadLogo(index, value.logo, true, false);
    });    
    
    //Load all team logos.
    $.each(this.games, function(index, value) {
	self.loadLogo(index, value.visitor.logo, false, true);
    });
    
    $.each(this.games, function(index, value) {
	self.loadLogo(index, value.home.logo, false, false);
    });
}
RiseVision.Common.Sports.prototype.loadLogo = function(index, url, isSport, isVisitor) {
    var self = this,
	img = new Image();

    img.onload = function () {
	self.logoCount++;
	self.onImageLoaded.call(self);
    }
    
    img.onerror = function() {
	if (isSport) {
	    self.sports[index].logo = "";
	}
	else if (isVisitor) {
	    self.games[index].visitor.logo = "";
	}
	else {
	    self.games[index].home.logo = "";
	}
	
	self.logoCount++;
	self.onImageLoaded.call(self);
    }
    
    //TODO: Add error checking as logo might not have been set at all?
    //Right now, http://www-open-opensocial.googleusercontent.com/gadgets/undefined shows up in console when url is undefined
    //(i.e. when no logo property exists for that sport/game).
    img.src = url;
}
RiseVision.Common.Sports.prototype.onImageLoaded = function() {
    if (this.logoCount >= this.totalLogos) {
	var self = this;
    
	setTimeout(function() {
	    $(self).trigger("updateTimerExpired");
	}, this.updateInterval);
	
	$(this).trigger("imagesLoaded");
    }
}

/*
 * Scroll horizontally on HTML5 canvas.
 */
RiseVision.Common.HorizontalScroll = function(settings, data) {
    //Private variables
    var scrollers = [],
	totalPixels = 0,f
	isStopped = false,
	interactivityTimerID = null;
    
    //Public variables
    this.scroller = document.createElement("canvas");
    this.scroller.id = "scroller"
    this.scroller.width = settings.width;
    this.scroller.height = settings.height;
    //this.scroller.height = 75;
    this.interactivityTimeout = settings.interactivityTimeout;
    this.context = this.scroller.getContext("2d");
    this.data = data;
    this.isHolding = false;
    this.isLoading = true;
    
    this.items = [];
    this.previousItemIndex = -1;
    this.itemCount = 0;
    this.currentItemIndex = 0;
    
    this.mouseDown = false;
    this.mouseMove = false;
    this.lastMouseX = 0;
    
    //Number of pixels to move per each redraw.
    if (settings.speed) {
	if (settings.speed == "fastest") {
	    this.speed = 5;
	}
	else if (settings.speed == "fast") {
	    this.speed = 4;
	}
	else if (settings.speed == "medium") {
	    this.speed = 3;
	}
	else if (settings.speed == "slow") {
	    this.speed = 2;
	}
	else if (settings.speed == "slowest") {
	    this.speed = 1;
	}
    }
    else {
	this.speed = 3;	//Backwards compatability.
    }
    
    if (settings.scrollDirection == "ltr") {
	this.speed = -this.speed;
    }
    
    //Getters
    this.getScrollBy = function() { return settings.scrollBy; }
    this.getScrollDirection = function() { return settings.scrollDirection; }
    this.getDuration = function() { return settings.duration; }
    this.getScroller = function(index) { return scrollers[index]; }
    this.getScrollers = function() { return scrollers; }
    this.getTotalPixels = function() { return totalPixels; }
    this.getIsStopped = function() { return isStopped; }
    this.getInteractivityTimerID = function() { return interactivityTimerID; }
    this.getSpacing = function() { return settings.spacing; }
    
    //Setters
    this.setScroller = function(index, value) { scrollers[index] = value; }
    this.setTotalPixels = function(value) { totalPixels = value; }
    this.setIsStopped = function(value) { isStopped = value; }
    
    document.body.appendChild(this.scroller);
    
    window.requestAnimFrame = (function(){
	return  window.requestAnimationFrame       || 
		window.webkitRequestAnimationFrame || 
		window.mozRequestAnimationFrame    || 
		window.oRequestAnimationFrame      || 
		window.msRequestAnimationFrame     ||
		function(/* function */ callback, /* DOMElement */ element){
		    return window.setTimeout(callback, 1000 / 60);
		};
    })();
    
    window.cancelRequestAnimFrame = ( function() {
	return window.cancelAnimationFrame          ||
	    window.webkitCancelRequestAnimationFrame    ||
	    window.mozCancelRequestAnimationFrame       ||
	    window.oCancelRequestAnimationFrame     ||
	    window.msCancelRequestAnimationFrame        ||
	    clearTimeout
    })();
}
//Create separate canvas for each data item.
RiseVision.Common.HorizontalScroll.prototype.initialize = function() {
    var text = "",
	self = this;
    
    this.scroller.onmousemove = function(e) {
	self.handleMouseMove(e);
    }
    
    this.scroller.onmousedown = function(e) {
	self.handleMouseDown(e);
    }
    
    this.scroller.onmouseup = function(e) {
	self.handleMouseUp(e);
    }
    
    this.scroller.onmouseout = function(e) {
	self.handleMouseOut(e);
    }
    
    //Create 2 Scroller objects.
    for (var i = 0; i < 2; i++) {
	if (this.getScrollDirection() == "rtl") {
	    this.setScroller(i, new RiseVision.Common.Scroller(i * this.scroller.width, this.scroller.width, this.scroller.height));
	}
	else {
	    this.setScroller(i, new RiseVision.Common.Scroller(-i * this.scroller.width, this.scroller.width, this.scroller.height));
	}
    }
    
    this.totalWidth = 0;
    this.itemsCount = 1;
    
    var length = this.data.length;
    
    if (length > 0) {
	this.loadItem();
    }
}
RiseVision.Common.HorizontalScroll.prototype.loadItem = function() {
    var item = new RiseVision.CommonItem(this.data[this.currentItemIndex], this.scroller, this.getSpacing(), this.getScrollDirection()),
	self = this;
    
    item.initialize(function() {
	self.items[self.currentItemIndex] = this;
	self.totalWidth += this.getWidth();
	self.onItemInitialized();
    });
}
RiseVision.Common.HorizontalScroll.prototype.onItemInitialized = function() {
    this.itemCount++;
	
    //All items have been loaded.
    if (this.itemCount == this.data.length) {
	for (var i = 0; i < this.getScrollers().length; i++) {
	    this.adjustCanvas(i);
	}
	
	this.currentItemIndex = 0;
	
	readyEvent();
    }
    else {
	this.currentItemIndex++;
	this.loadItem();
    }
}
RiseVision.Common.HorizontalScroll.prototype.adjustCanvas = function(i, swipeDirection) {
    var itemPosition = 0,
	isCopied = false,
	isMovingForward = true;
    
    if (!swipeDirection) {	//Auto-scroll
	swipeDirection = this.getScrollDirection();
    }
    
    if (this.getScrollDirection() == "rtl") {
	if (swipeDirection == "rtl") {
	    this.getScroller(i).writeDirection = "forward";
	}
	else {
	    this.getScroller(i).writeDirection = "backward";
	}
    }
    else {
	if (swipeDirection == "rtl") {
	    this.getScroller(i).writeDirection = "backward";
	}
	else {
	    this.getScroller(i).writeDirection = "forward";
	}
    }
    
    if (this.getScrollBy() == "item") {
	var j, index;
	
	//Get position at which to start copying based on position of other scroller at which copying was stopped.
	this.getScroller(i).holdPositions = [];
	itemPosition = this.getItemPosition(this.getNextScrollerIndex(i), swipeDirection);
	
	//Copy until the scroller is filled, or until we have finished copying all of the text associated with the current ID.
	while (this.getScroller(i).totalPixelsCopied < this.getScroller(i).canvas.width) {
	    if (this.getScroller(i).totalPixelsCopied == 0) {
		if (((this.getScrollDirection() == "rtl") && (swipeDirection == "rtl")) || ((this.getScrollDirection() == "ltr") && (swipeDirection == "rtl"))) {
		    //Save the index of the first item that is being copied.
		    this.getScroller(i).startCanvasIndex = this.currentItemIndex;
		    this.getScroller(i).startCanvasItem = this.items[this.currentItemIndex];
		}
		else {
		    this.getScroller(i).endCanvasIndex = this.currentItemIndex;
		    this.getScroller(i).endCanvasItem = this.items[this.currentItemIndex];
		}
	    }
	    
	    if (this.currentItemIndex != this.previousItemIndex) {
		if (this.getScrollDirection() == "rtl") {
		    if (swipeDirection == "rtl") {
			if (this.getScroller(i).writeDirection == "forward") {
			    this.getScroller(i).holdPositions.push({position: this.getScroller(i).writePosition, wasHeld: false});
			}
		    }
		    else {
			if (itemPosition != 0) {
			    this.getScroller(i).holdPositions.push({position: this.getScroller(i).writePosition, wasHeld: false});
			}
		    }
		}
		else {
		    this.getScroller(i).holdPositions.push({position: this.getScroller(i).writePosition, wasHeld: false});
		}
	    }
    
	    if (this.getScrollDirection() == "rtl") {
		if (swipeDirection == "rtl") {
		    isCopied = this.getScroller(i).drawCanvasFromStart(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection());

		    //If the scroller is filled and the ending position is 0, move to the next item.
		    if (this.getScroller(i).endCanvasPosition == 0 && this.getScroller(i).writePosition == 0) {
			this.setNextItem(this.currentItemIndex);
		    }	    
		    
		    this.getScroller(i).endCanvasIndex = this.currentItemIndex;
		    this.getScroller(i).endCanvasItem = this.items[this.currentItemIndex];
		}
		else {
		    isCopied = this.getScroller(i).drawCanvasFromEnd(this.items[this.currentItemIndex].canvas, itemPosition);
		    this.getScroller(i).startCanvasIndex = this.currentItemIndex;
		    this.getScroller(i).startCanvasItem = this.items[this.currentItemIndex];
		}
	    }
	    else {
		if (swipeDirection == "ltr") {
		    isCopied = this.getScroller(i).drawCanvasFromEnd(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection());
		    this.getScroller(i).startCanvasIndex = this.currentItemIndex;
		    this.getScroller(i).startCanvasItem = this.items[this.currentItemIndex];
		}
		else {
		    isCopied = this.getScroller(i).drawCanvasFromStart(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection());
		    this.getScroller(i).endCanvasIndex = this.currentItemIndex;
		    this.getScroller(i).endCanvasItem = this.items[this.currentItemIndex];
		}
	    }
	    
	    if (isCopied) {	//This item has been copied. Copy the next item if it shares the same id.
		if (this.getScroller(i).writeDirection == "forward") {
		    this.setNextItem(this.currentItemIndex);
		}
		else {
		    this.setPreviousItem(this.currentItemIndex);
		}
	    }
	    
	    itemPosition = 0;
	}
    }
    else {
	itemPosition = this.getItemPosition(this.getNextScrollerIndex(i), swipeDirection);
	
	while (this.getScroller(i).totalPixelsCopied < this.getScroller(i).canvas.width) {
	    //Save the index of the first canvas that is being copied.
	    if (this.getScroller(i).totalPixelsCopied == 0) {
		if (((this.getScrollDirection() == "rtl") && (swipeDirection == "rtl")) || ((this.getScrollDirection() == "ltr") && (swipeDirection == "rtl"))) {
		    this.getScroller(i).startCanvasIndex = this.currentItemIndex;
		}
		else {
		    this.getScroller(i).endCanvasIndex = this.currentItemIndex;
		}
	    }

	    if (this.getScrollDirection() == "rtl") {
		if (swipeDirection == "rtl") {
		    isCopied = this.getScroller(i).drawCanvasFromStart(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection());
		}
		else {
		    isCopied = this.getScroller(i).drawCanvasFromEnd(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection(), this.getScroller(this.getNextScrollerIndex(i)).writeDirection);
		}
	    }
	    else {
		if (swipeDirection == "rtl") {
		    isCopied = this.getScroller(i).drawCanvasFromStart(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection());
		}
		else {
		    isCopied = this.getScroller(i).drawCanvasFromEnd(this.items[this.currentItemIndex].canvas, itemPosition, this.getScrollDirection(), this.getScroller(this.getNextScrollerIndex(i)).writeDirection);
		}
	    }
	
	    if (isCopied) {
		if (this.getScroller(i).writeDirection == "forward") {
		    this.setNextItem(this.currentItemIndex);
		}
		else {
		    this.setPreviousItem(this.currentItemIndex);
		}
	    }
	    
	    itemPosition = 0;
	}
	
	//Save the index of the last canvas that is being copied.
	if (((this.getScrollDirection() == "rtl") && (swipeDirection == "rtl")) || ((this.getScrollDirection() == "ltr") && (swipeDirection == "rtl"))) {
	    this.getScroller(i).endCanvasIndex = this.currentItemIndex;
	}
	else {
	    this.getScroller(i).startCanvasIndex = this.currentItemIndex;
	}
    }
    
    this.isLoading = false;
    this.getScroller(i).totalPixelsCopied = 0;
}
RiseVision.Common.HorizontalScroll.prototype.getItemPosition = function(j, swipeDirection) {
    var itemPosition;

    if (this.getScrollDirection() == "rtl") {
	if (swipeDirection == "rtl") {
	    //Row, Left, auto-scroll
	    itemPosition = this.getPositionFromEnd(j, swipeDirection);
	}
	else {
	    //Row, Left, swipe in opposite direction
	    itemPosition = this.getPositionFromStart(j, swipeDirection);
	}
    }
    else {
	if (swipeDirection == "rtl") {
	    //Row, Right, swipe in opposite direction
	    itemPosition = this.getPositionFromEnd(j, swipeDirection);
	}
	else {
	    //Row, Right, auto-scroll
	    itemPosition = this.getPositionFromStart(j, swipeDirection);
	}
    }
    
    return itemPosition;
}
RiseVision.Common.HorizontalScroll.prototype.getPositionFromStart = function(j, swipeDirection) {
    var itemPosition;
    
    itemPosition = this.getScroller(j).startCanvasPosition;
    this.currentItemIndex = this.getScroller(j).startCanvasIndex;
    
    if (this.getScrollDirection() == "rtl" && swipeDirection == "ltr") {
	//If we're at the very beginning of a canvas, move to the previous canvas.
	if (itemPosition == 0) {
	    this.setPreviousItem(this.getScroller(j).startCanvasIndex);
	}
    }
    else if (this.getScrollDirection() == "ltr" && swipeDirection == "ltr") {
	//If we're at the very beginning of a canvas, move to the previous canvas.
	if (!this.isLoading) {
	    if (itemPosition == 0) {
		this.setNextItem(this.getScroller(j).startCanvasIndex);
	    }
	}
    }
    
    if (!this.isLoading) {
	this.previousItemIndex = this.currentItemIndex;
    }
    
    return itemPosition;
}
RiseVision.Common.HorizontalScroll.prototype.getPositionFromEnd = function(j, swipeDirection) {
    var itemPosition;
    
    this.currentItemIndex = this.getScroller(j).endCanvasIndex;
    itemPosition = this.getScroller(j).endCanvasPosition;
    
    if (this.getScrollDirection() == "ltr" && swipeDirection == "rtl") {
	if (itemPosition == this.items[this.currentItemIndex].canvas.width) {
	    this.setPreviousItem(this.getScroller(j).endCanvasIndex);
	    itemPosition = 0;
	}
    }
    else if (this.getScrollDirection() == "rtl" && swipeDirection == "rtl") {
	//If we're at the very end of a canvas, move to the next canvas.
	if (itemPosition == this.items[this.currentItemIndex].canvas.width) {
	    this.setNextItem(this.getScroller(j).endCanvasIndex);
	    itemPosition = 0;
	}
    }
    
    if (!this.isLoading) {
	this.previousItemIndex = this.currentItemIndex;
    }
	
    return itemPosition;
}
RiseVision.Common.HorizontalScroll.prototype.getNextScrollerIndex = function(index) {
    var next = ++index;
    
    if (next >= this.getScrollers().length) {
	next = 0;
    }
    
    return next;
}
RiseVision.Common.HorizontalScroll.prototype.setNextItem = function(index) {
    var next = ++index;
    
    if (next >= this.items.length) {
	next = 0;
    }
    
    this.currentItemIndex = next;
    
    return next;
}
RiseVision.Common.HorizontalScroll.prototype.setPreviousItem = function(index) {
    var previous = --index;
    
    if (previous < 0) {
	previous = this.items.length - 1;
    }
    
    this.currentItemIndex = previous;
    
    return previous;
}
RiseVision.Common.HorizontalScroll.prototype.drawScene = function() {
    var self = this,
	difference;
    
    //if (!this.getMouseDown() && !this.isStopped) {
    if (!this.mouseDown && !this.isStopped) {
	this.context.clearRect(0, 0, this.scroller.width, this.scroller.height);
	
	for (var i = 0; i < this.getScrollers().length; i++) {
	    var scroller = this.getScroller(i);
	    
	    scroller.x = scroller.x - this.speed;
	    
	    if (this.getScrollDirection() == "rtl") {
		difference = scroller.x + this.scroller.width;
	    }
	    else {
		difference = scroller.x - this.scroller.width;
	    }
	    
	    if ((difference < 0) && (this.getScrollDirection() == "rtl")) {
		//Move canvas to the end.
		scroller.x = this.scroller.width;
		scroller.x = scroller.x - (-difference);
		this.adjustCanvas(i);
	    }
	    else if ((difference > 0) && (this.getScrollDirection() == "ltr")) {
		//Move canvas to the start.
		scroller.x = -this.scroller.width;
		scroller.x = scroller.x - (-difference);
		this.adjustCanvas(i);
	    }
	    
	    this.drawCanvas(scroller.x, i);    	    		
	}
	
	this.setTotalPixels(this.getTotalPixels() + Math.abs(this.speed));
	
	//PUD is implemented by counting the number of pixels that have been scrolled.
	if (this.getTotalPixels() > this.totalWidth) {
	    this.setTotalPixels(this.getTotalPixels() - this.totalWidth);
	    $(this).trigger("done");
	}
    }
}
RiseVision.Common.HorizontalScroll.prototype.drawSceneByItem = function() {
    //if (!this.getMouseDown() && !this.isStopped) {
    if (!this.mouseDown && !this.isStopped) {
	var difference = 0;
	
	//Check if either of the Scrollers should be held.
	for (var i = 0; i < this.getScrollers().length; i++) {
	    var scroller = this.getScroller(i);
	    
	    if (scroller.holdPositions.length > 0) {
		for (var j = 0; j < scroller.holdPositions.length; j++) {
		    if ((scroller.x <= -scroller.holdPositions[j].position) && !scroller.holdPositions[j].wasHeld && this.getScrollDirection() == "rtl") {
			//Position scroller at the hold position.
			difference = scroller.x + scroller.holdPositions[j].position;
			scroller.x = -scroller.holdPositions[j].position;
			this.holdScroller(scroller, i, j);
			
			break;
		    }
		    else if ((scroller.x >= scroller.holdPositions[j].position) && !scroller.holdPositions[j].wasHeld && this.getScrollDirection() == "ltr") {
			//Position scroller at the hold position.
			difference = scroller.x - scroller.holdPositions[j].position;
			scroller.x = scroller.holdPositions[j].position;
			this.holdScroller(scroller, i, j);
			
			break;
		    }
		    else {
			this.isHolding = false;
		    }
		}
		
		if (this.isHolding) {
		    //Adjust other scroller by the same number of pixels.
		    var index = this.getNextScrollerIndex(i);
		    
		    this.getScroller(index).x = this.getScroller(index).x - difference;
		    this.moveCanvas(i);
		    this.drawCanvas(this.getScroller(index).x, index);
			
		    break;
		}
	    }
	    else {
		this.isHolding = false;
	    }
	}
	
	//Draw only if the scroller is not holding.
	if (!this.isHolding) {
	    this.context.clearRect(0, 0, this.scroller.width, this.scroller.height);
	    
	    for (var i = 0; i < this.getScrollers().length; i++) {
		var scroller = this.getScroller(i),
		    newX = scroller.x - this.speed;
		    
		    scroller.x = newX;
		    this.moveCanvas(i);
		    this.drawCanvas(scroller.x, i);
	    }
	}
    }
}
RiseVision.Common.HorizontalScroll.prototype.holdScroller = function(scroller, i, j) {
    scroller.holdPositions[j].wasHeld = true;
			
    this.context.clearRect(0, 0, this.scroller.width, this.scroller.height);
    this.moveCanvas(i);
    this.drawCanvas(scroller.x, i);
    this.setHoldTimer();
    this.isHolding = true;
}
RiseVision.Common.HorizontalScroll.prototype.moveCanvas = function(i) {
    var scroller = this.getScroller(i),
	difference;
    
    if (this.getScrollDirection() == "rtl") {
	difference = scroller.x + this.scroller.width;
    }
    else {
	difference = scroller.x - this.scroller.width;
    }

    //Move canvas to the end.
    if ((difference < 0) && (this.getScrollDirection() == "rtl")) {
	scroller.x = this.scroller.width;
	scroller.x = scroller.x - (-difference);
	this.adjustCanvas(i);
    }
    //Move canvas to the beginning.
    else if ((difference > 0) && (this.getScrollDirection() == "ltr")) {
	scroller.x = -this.scroller.width;
	scroller.x = scroller.x - (-difference);
	this.adjustCanvas(i);
    }
}
//Draw entire Scroller piece onto scroller at 0, 0.
RiseVision.Common.HorizontalScroll.prototype.drawCanvas = function(x, i) {
    var canvas = this.getScroller(i).canvas;
    
    this.context.save();
    this.context.translate(x, 0);
    this.context.drawImage(canvas, 0, 0, canvas.width, canvas.height);
    this.context.restore(); 
}
RiseVision.Common.HorizontalScroll.prototype.setHoldTimer = function() {
    var self = this;
    
    clearTimeout(this.holdTimerID);

    //PUD is implemented by counting the number of items that have been shown.
    if (this.itemsCount > this.items.length - 1) {
	this.itemsCount = 0;
	$(this).trigger("done");
    }
    else {
	this.isHolding = true;
	this.isStopped = true;
	this.holdTimerID = setTimeout(function() {
	    self.isHolding = false;
	    self.isStopped = false;
	    self.itemsCount++;
	}, this.getDuration());    
    }
}
RiseVision.Common.HorizontalScroll.prototype.handleMouseDown = function(event) {
    this.mouseDown = true;
    this.lastMouseX = event.clientX;
}
RiseVision.Common.HorizontalScroll.prototype.handleMouseUp = function(event) {
    var self = this;
    
    this.mouseDown = false;
    
    if (!this.mouseMove) {
	clearTimeout(this.interactivityTimerID);
	this.isStopped = true;
	this.interactivityTimerID = setTimeout(function() {
	    self.isStopped = false;
	}, this.interactivityTimeout);
    }
    else {
	this.mouseMove = false;
    }
}
RiseVision.Common.HorizontalScroll.prototype.handleMouseOut = function(event) {
    this.mouseDown = false;
}
RiseVision.Common.HorizontalScroll.prototype.handleMouseMove = function(event) {
    if (!this.mouseDown) {
	return;
    }
    
    clearTimeout(this.holdTimerID);
    this.isHolding = false;
    
    var newX = event.clientX,
	deltaX = this.lastMouseX - newX,
	difference;
    
    this.mouseMove = true;
    this.context.clearRect(0, 0, this.scroller.width, this.scroller.height);
    
    for (var i = 0; i < this.getScrollers().length; i++) {
	var scroller = this.getScroller(i);
	
	scroller.x = scroller.x - deltaX;
	
	if (this.getScrollDirection() == "rtl") {
	    if (deltaX > 0) {	//Swipe left
		difference = scroller.x + this.scroller.width;
		
		if (difference < 0) {
		    scroller.x = this.scroller.width;
		    scroller.x = scroller.x - (-difference);		
		    this.adjustCanvas(i, "rtl");
		}
	    }
	    else if (deltaX < 0) {	//Swipe right
		difference = scroller.x - this.scroller.width;
		
		if (difference > 0) {
		    scroller.x = -this.scroller.width;
		    scroller.x = scroller.x - (-difference);		
		    this.adjustCanvas(i, "ltr");
		}
	    }
	    
	    //Flag hold position(s) as having been held when swiping past it.
	    for (var j = 0; j < scroller.holdPositions.length; j++) {
		if ((scroller.x <= -scroller.holdPositions[j].position) && !scroller.holdPositions[j].wasHeld) {
		    scroller.holdPositions[j].wasHeld = true;
		}
	    }
	}
	else {	//right
	    if (deltaX > 0) {	//Swipe left
		difference = scroller.x + this.scroller.width;
		
		if (difference < 0) {
		    scroller.x = this.scroller.width;
		    scroller.x = scroller.x - (-difference);		
		    this.adjustCanvas(i, "rtl");
		}
	    }
	    else if (deltaX < 0) {	//Swipe right
		difference = scroller.x - this.scroller.width;
		
		if (difference > 0) {
		    scroller.x = -this.scroller.width;
		    scroller.x = scroller.x - (-difference);		
		    this.adjustCanvas(i, "ltr");
		}
	    }
	    
	    //Flag hold position(s) as having been held when swiping past it.
	    //Prevent scroller from snapping back to hold position if user has swiped past it.
	    for (var j = 0; j < scroller.holdPositions.length; j++) {
		if ((scroller.x <= -scroller.holdPositions[j].position) && !scroller.holdPositions[j].wasHeld && scroller.writeDirection == "forward") {
		    scroller.holdPositions[j].wasHeld = true;
		}
		else if ((scroller.x >= scroller.holdPositions[j].position) && !scroller.holdPositions[j].wasHeld && scroller.writeDirection == "backward") {
		    scroller.holdPositions[j].wasHeld = true;
		}
	    }
	}
	
	this.drawCanvas(scroller.x, i);
    }
    
    this.isStopped = false;
    this.lastMouseX = newX;
}
RiseVision.Common.HorizontalScroll.prototype.tick = function() {
    var self = this;
    
    this.request = requestAnimFrame(function() {
	self.tick();
    });
    
    if (this.getScrollBy() == "item") {
	this.drawSceneByItem();
    }
    else {
	this.drawScene();
    }
}
RiseVision.Common.HorizontalScroll.prototype.pause = function() {
    cancelRequestAnimFrame(this.request); 
}
RiseVision.Common.HorizontalScroll.prototype.updateItem = function(index, data, callback) {
    var self = this,
	oldItem = this.items[index],
	newItem = null;
    
    if (!oldItem) {
	console.log(index);
    }
    else {
	this.totalWidth -= oldItem.getWidth();
	this.items[index] = null;
	oldItem.destroy();

	newItem = new RiseVision.Common.Item(data, this.scroller, this.getSpacing(), this.getScrollDirection(), index);
	newItem.initialize(function() {
	    self.items[index] = this;
	    self.totalWidth += this.getWidth();
	    newItem = null;
	    
	    if (callback) {
		callback();
	    }
	});
    }
}

//var svgCanvases = [];
RiseVision.Common.Item = function(data, scroller, spacing, scrollDirection, position, isRefreshing) {
    this.svgCanvases = [];
    this.canvas = document.createElement("canvas");
    this.canvas.className = "item";
    this.context = this.canvas.getContext("2d");
    this.context.canvas.height = scroller.height;
    
    this.data = data;	//Array of JSON objects representing contents to be drawn for a single item.
    this.index = 0;
    this.width = 0;
    this.writePosition = 0;
    this.scroller = scroller;
    this.spacing = spacing;
    this.scrollDirection = scrollDirection;
    this.isRefreshing = isRefreshing;
    this.dataIndex = 0;
    
    if (typeof position === "undefined") {
	this.position = -1;
    }
    else {
	this.position = position;
    }
}
RiseVision.Common.Item.prototype.getWidth = function() {
    return this.context.canvas.width;
}
RiseVision.Common.Item.prototype.initialize = function(callback) {
    this.callback = callback;
    this.getImage();
}
RiseVision.Common.Item.prototype.getImage = function() {
    var self = this,
	data = this.data[this.dataIndex];
    
    if (data) {
	//Check if there are any images to load.
	if (data.type == "image") {
	    this.index = this.dataIndex;
	    
	    //First check if the image has been cached.
	    if (this.svgCanvases.length > 0) {
		$.each(this.svgCanvases, function(index, canvas) {
		    if (canvas.url == data.value) {
			data.svg = self.svgCanvases[index].canvas;
			return false;
		    }
		});
		
		if (data.svg) {
		    this.width += data.svg.width + this.spacing;
		    this.dataIndex++;
		    this.getImage();
		}
		else {
		    this.loadImage(data.value);
		}
	    }
	    else {
		this.loadImage(data.value);
	    }
	}
	else {	//Text
	    this.createTempText(this.data[this.dataIndex].value, this.data[this.dataIndex].fontRule);
	    this.dataIndex++;
	    this.getImage();
	}
    }
    else {	//All images loaded.
	this.drawCanvas();
    }
}
RiseVision.Common.Item.prototype.loadImage = function(url) {
    var params = {},
	self = this;
	
    //Need to use makeRequest to get around cross-domain issues for loading SVG images.
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
    gadgets.io.makeRequest(url, function(obj) {
	//Save the SVG data.
	if (obj.data) {
	    var data = self.data[self.dataIndex];
	    
	    data.svg = obj.data;
	    self.createTempImage(self.dataIndex, data.svg, data.value);
	}
	
	self.dataIndex++;
	self.getImage();
    }, params);
    
    //Load regular images.
//    var image = new Image();
//    
//    image.onload = function() {
//	callback(this);
//    };
//    image.onerror = function(e) {
//	callback(this);
//    };	
//    image.crossOrigin = 'anonymous'; // no credentials flag
//    image.src = this.logo;
//    this.image = image;
}
//Necessary for getting the width of the image when drawn onto the canvas.
RiseVision.Common.Item.prototype.createTempImage = function(i, svg, url) {
    var svgCanvas = document.createElement("canvas"),	//Canvas on which the SVG image will be drawn.
	svgContext = svgCanvas.getContext("2d"),
	id = "svg";
   
    svgCanvas.id = id;
    svgCanvas.height = this.scroller.height - 10;	//Leave 5px of padding at top and bottom.
    svgCanvas.width = this.scroller.height - 10;		//Necessary in order for scaling to work.

    document.body.appendChild(svgCanvas);
    
    //Draw the image and scale the height to fill the scroller.
    canvg(id, svg, { scaleHeight: true, ignoreDimensions: true });
    
    this.width += svgCanvas.width + this.spacing;
    this.data[i].svg = svgCanvas;
    
    this.svgCanvases.push({
	"url": url,
	"canvas": svgCanvas
    });

    document.body.removeChild(svgCanvas);
}
RiseVision.Common.Item.prototype.createImage = function(i, svg) {
    //Scale the non-SVG image if necessary.
//    var ratio = 1;
//   
//    if (this.image.height > scroller.height) {
//	ratio = scroller.height / this.image.height;
//    }
//    else if (this.image.width > scroller.width) {
//	ratio = scroller.width / this.image.width;
//    }

    //Draw the image after the text and starting 5px from the top.
    if (this.scrollDirection == "rtl") {
	this.context.drawImage(this.data[i].svg, 0, 0, this.data[i].svg.width, this.data[i].svg.height, this.writePosition, 5, this.data[i].svg.width, this.data[i].svg.height);
	this.writePosition += this.data[i].svg.width + this.spacing;
    }
    else {
	this.context.drawImage(this.data[i].svg, 0, 0, this.data[i].svg.width, this.data[i].svg.height, this.writePosition + this.spacing, 5, this.data[i].svg.width, this.data[i].svg.height);
	this.writePosition += this.data[i].svg.width + this.spacing;
    }
}
/* Text is written to a temporary canvas first so that the width of the text can be determined.
   This is then used to set the width of the actual canvas, which needs to be done before being written to. */
RiseVision.Common.Item.prototype.createTempText = function(value, fontRule) {
    var textCanvas = document.createElement("canvas"),
	textContext = textCanvas.getContext("2d");
	
    this.writeText(value, fontRule, textContext);
    this.width += textContext.measureText(value).width + this.spacing;
}
/* Write the text to the actual canvas. */
RiseVision.Common.Item.prototype.createText = function(value, fontRule) {
    this.writeText(value, fontRule, this.context);
    this.writePosition += this.context.measureText(value).width + this.spacing;
}
RiseVision.Common.Item.prototype.writeText = function(text, fontRule, context) {
    var topOffset = context.canvas.height / 2,	//Y coordinate at which to being drawing (vertical alignment).
	rules, canvasFont;
    
    rules = this.parseCSSRule(fontRule);
    canvasFont = rules[2] + rules[3] + rules[0];
    
    context.font = canvasFont;
    context.strokeStyle = rules[1];
    context.textAlign = "left";
    context.textBaseline = "middle";
    
    context.save();
    context.translate(0, topOffset);
    
    context.fillStyle = rules[1];
    
    if (this.scrollDirection == "rtl") {
	context.fillText(text, this.writePosition, 0);
    }
    else {	//ltr
	context.fillText(text, this.writePosition + this.spacing, 0);
    }

    context.restore();
}
RiseVision.Common.Item.prototype.drawCanvas = function() {
    var length = this.data.length;
    
    this.context.canvas.width = this.width;
    //this.context.canvas.style.display = "none";
    
    //Draw to canvas.
    for (var i = 0; i < length; i++) {
	if (this.data[i].type == "text") {
	    this.createText(this.data[i].value, this.data[i].fontRule);
	}
	else if (this.data[i].type == "image") {
	    if (this.data[i].svg) {
		this.createImage(i, this.data[i].svg);
	    }
	}
    }
    
    this.addCanvas();
    this.callback();
}
RiseVision.Common.Item.prototype.addCanvas = function() {
    if (this.position != -1) {
	var $item = $(".item").eq(this.position);
	
	if ($item.length > 0) {
	    $(this.canvas).insertBefore($item);
	}
	else {	//Add it to the end.
	    document.body.appendChild(this.canvas);
	}
    }
    else {
	document.body.appendChild(this.canvas);
    }
}
RiseVision.Common.Item.prototype.destroy = function() {
    document.body.removeChild(this.canvas);
    this.context = null;
    this.canvas = null;
    this.data = null;
    this.scroller = null;
    this.callback = null;
}
RiseVision.Common.Item.prototype.getFontHeight = function(fontStyle) {
    var body = document.getElementsByTagName("body")[0],
	dummy = document.createElement("div"),
	dummyText = document.createTextNode("M"),
	result;
    
    dummy.setAttribute("style", fontStyle);
    body.appendChild(dummy);
    dummy.appendChild(dummyText);
    
    result = dummy.offsetHeight;
    
    dummy.removeChild(dummyText);
    body.removeChild(dummy);
    
    body = null;
    dummy = null;
    dummyText = null;
    
    return result;
}
//TODO: Find a better way to parse this and make this code common.
RiseVision.Common.Item.prototype.parseCSSRule = function(fontRule) {
    var a = fontRule.indexOf("{"),
	b = fontRule.indexOf("}"),
	selector = fontRule.substring(0, a),
	rules = fontRule.substring(++a, b).split(";"),
	values = [];
    
    //Now remove property name and just keep the value.
    for (var i = 0; i < rules.length; i++) {
	values.push(rules[i].substring(rules[i].indexOf(":", 0) + 1));
    }

    return values;
}

/* Parent class for all scrollers. */
RiseVision.Common.Scroller = function(xPos, width, height) {
    this.canvas = document.createElement("canvas");
    this.canvas.width = width;
    this.canvas.height = height;
    this.context = this.canvas.getContext("2d");
    
    this.x = xPos;
    this.startCanvasItem = null;
    this.endCanvasItem = null;
    this.startCanvasIndex = 0;		//Index of the first canvas to be copied onto the scroller.
    this.endCanvasIndex = 0;		//Index of the last canvas to be copied onto the scroller.
    this.startCanvasPosition = 0;	//Position at which the canvas at index startCanvasIndex started being copied.
    this.endCanvasPosition = 0;		//Position at which the canvas at index endCanvasIndex finished being copied.
    this.writeDirection = "forward";
    this.totalPixelsCopied = 0;
    this.writePosition = 0;
    this.holdPositions = [];
    
    //document.body.appendChild(document.createElement("div"));
    //document.body.appendChild(this.canvas);
}
/* Draw starting from beginning of scroller. */
RiseVision.Common.Scroller.prototype.drawCanvasFromStart = function(canvas, currentItemPosition, scrollDirection) {	//canvas = item's canvas
    var context2D = canvas.getContext("2d"),
	pixelsRemaining = this.canvas.width - this.totalPixelsCopied,
	isCanvasCopied = false,
	pixelsCopied = 0,
	imageData = null,
	width;
	
    //Only set this on first time through.
    if (this.totalPixelsCopied == 0) {
	this.startCanvasPosition = currentItemPosition;
	this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
    
    if (scrollDirection == "rtl") {
	if (currentItemPosition == 0) {	//All canvases except first one to be written.
	    width = canvas.width;
	}
	else {
	    width = canvas.width - currentItemPosition;
	}
    }
    else {
	width = canvas.width - currentItemPosition;
    }

    //Content that remains to be shown is shorter than the scroller.
    if (width <= pixelsRemaining) {
	if (width > 0) {
	    imageData = context2D.getImageData(currentItemPosition, 0, width, canvas.height);
	}
	
	pixelsCopied = width;
	this.totalPixelsCopied += pixelsCopied;
	currentItemPosition = 0;
	isCanvasCopied = true;
    }
    else {
	imageData = context2D.getImageData(currentItemPosition, 0, pixelsRemaining, canvas.height);
	pixelsCopied = pixelsRemaining;
	this.totalPixelsCopied += pixelsRemaining;
	currentItemPosition += pixelsRemaining;
    }

    //Paint the pixel data into the context.
    if (imageData) {
	this.context.putImageData(imageData, this.writePosition, 0);
    }
    
    this.writePosition += pixelsCopied;
    this.endCanvasPosition = currentItemPosition;	//Indicates how many pixels have been copied already.
    
    if (this.totalPixelsCopied >= this.canvas.width) {
	this.writePosition = 0;
    }
    
    imageData = null;
    
    return isCanvasCopied;
}
/* Draw starting from end of scroller. */
RiseVision.Common.Scroller.prototype.drawCanvasFromEnd = function(canvas, currentItemPosition) {
    var context2D = canvas.getContext("2d"),
	pixelsRemaining = this.canvas.width - this.totalPixelsCopied,
	isCanvasCopied = false, pixelsCopied = 0, imageData, width;
	
    if (currentItemPosition == 0) {	//All canvases except first one to be written.
	width = canvas.width;	
	currentItemPosition = width;
    }
    else {
	width = currentItemPosition;
    }
    
    //Only set this on first time through. We're working backwards here.
    if (this.totalPixelsCopied == 0) {
	this.endCanvasPosition = width;
	this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
    
    //Content that remains to be shown is shorter than the scroller.
    if (width <= pixelsRemaining) {
	imageData = context2D.getImageData(0, 0, width, canvas.height);
	pixelsCopied = width;
	this.totalPixelsCopied += pixelsCopied;
	currentItemPosition = 0;
	isCanvasCopied = true;
    }
    else {
	imageData = context2D.getImageData(width - pixelsRemaining, 0, pixelsRemaining, canvas.height);
	pixelsCopied = pixelsRemaining;
	this.totalPixelsCopied += pixelsRemaining;
	currentItemPosition -= pixelsRemaining;
    }

    //Paint the pixel data into the context.
    this.context.putImageData(imageData, this.canvas.width - this.totalPixelsCopied, 0);
    this.startCanvasPosition = currentItemPosition;	//Indicates how many pixels have been copied already.
    this.writePosition += pixelsCopied;
    
    if (this.totalPixelsCopied >= this.canvas.width) {
	this.writePosition = 0;
    }
    
    imageData = null;
    
    return isCanvasCopied;
}

/*
 * Dynamically load a CSS file.
 */
RiseVision.Common.Utility.loadCSS = function(url) {
    var link = $("<link>");	 
    
    link.attr({
	type: "text/css",
	rel: "stylesheet",
	href: url
    });
    
    $("head").append(link);
}
/*
 * Dynamically load a Javascript file.
 */
RiseVision.Common.Utility.loadJS = function(filename, callback) {
    var fileref = document.createElement("script");
    
    fileref.type = "text/javascript";
    fileref.onload = function() {
	if (callback) {
	    callback();
	}
    };
    
    fileref.src = filename;
    
    if (typeof fileref != "undefined") {
	document.getElementsByTagName("head")[0].appendChild(fileref);
    }
}
/*
 * Format a number to include commas.
 */
RiseVision.Common.Utility.addCommas = function(number) {
    var x, x1, x2, regex;
    
    number += '';
    x = number.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    regex = /(\d+)(\d{3})/;
    
    while (regex.test(x1)) {
	x1 = x1.replace(regex, '$1' + ',' + '$2');
    }
    
    return x1 + x2;
}
/*
 * Unescape HTML.
 */
RiseVision.Common.Utility.unescapeHTML = function(html) {
    var htmlNode = document.createElement("div");
   
    htmlNode.innerHTML = html;
   
    if (htmlNode.innerText !== undefined) {
	return htmlNode.innerText; // IE
    }
   
    return htmlNode.textContent;
}
/*
 * Scale an image down if necessary to fit within a particular area.
 */
RiseVision.Common.Utility.scaleToFit = function(settings) {
    var objImage = new Image();	
    
    //Use an Image object in order to get the actual dimensions of the image.				
    objImage.onload = function() {
	var imageWidth, imageHeight, ratioX, ratioY, scale, newWidth, newHeight;
	
	imageWidth = objImage.width;
	imageHeight = objImage.height;

	//Scale down images only. Don't scale up.
	if ((imageWidth > 0) && (imageHeight > 0) && ((imageWidth > settings.rsW) || (imageHeight > settings.rsH))) {
	    //Calculate scale ratios.
	    ratioX = settings.rsW / imageWidth;
	    ratioY = settings.rsH / imageHeight;
	    scale = ratioX < ratioY ? ratioX : ratioY;

	    //Calculate and set new image dimensions.
	    newWidth = parseInt(imageWidth * scale, 10);
	    newHeight = parseInt(imageHeight * scale, 10);
	    
	    //Call the callback function and pass the new dimensions.
	    settings.callback(newWidth, newHeight);
	}
	else {	//Pass the original dimensions unchanged.
	    settings.callback(imageWidth, imageHeight);
	}
    }
    
    //Call the error handler if the image could not be loaded.
    objImage.onerror = function() {
	settings.onerror(objImage);
    }

    objImage.setAttribute("src", settings.url);
}